// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE

#if !defined(JSON_IS_AMALGAMATION)
#include "nim_wrapper_util/json/writer.h"
#include "nim_wrapper_util/json_tool.h"
#endif  // if !defined(JSON_IS_AMALGAMATION)
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstring>
#include <iomanip>
#include <memory>
#include <set>
#include <sstream>
#include <utility>

#if __cplusplus >= 201103L
#include <cmath>
#include <cstdio>

#if !defined(isnan)
#define isnan std::isnan
#endif

#if !defined(isfinite)
#define isfinite std::isfinite
#endif

#else
#include <cmath>
#include <cstdio>

#if defined(_MSC_VER)
#if !defined(isnan)
#include <float.h>
#define isnan _isnan
#endif

#if !defined(isfinite)
#include <float.h>
#define isfinite _finite
#endif

#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
#endif  //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES

#endif  //_MSC_VER

#if defined(__sun) && defined(__SVR4)  // Solaris
#if !defined(isfinite)
#include <ieeefp.h>
#define isfinite finite
#endif
#endif

#if defined(__hpux)
#if !defined(isfinite)
#if defined(__ia64) && !defined(finite)
#define isfinite(x) ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x)))
#endif
#endif
#endif

#if !defined(isnan)
// IEEE standard states that NaN values will not compare to themselves
#define isnan(x) (x != x)
#endif

#if !defined(__APPLE__)
#if !defined(isfinite)
#define isfinite finite
#endif
#endif
#endif

#if defined(_MSC_VER)
// Disable warning about strdup being deprecated.
#pragma warning(disable : 4996)
#endif

namespace nim_cpp_wrapper_util {
namespace Json {

#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
using StreamWriterPtr = std::unique_ptr<StreamWriter>;
#else
using StreamWriterPtr = std::auto_ptr<StreamWriter>;
#endif

String valueToString(LargestInt value) {
    UIntToStringBuffer buffer;
    char* current = buffer + sizeof(buffer);
    if (value == Value::minLargestInt) {
        uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
        *--current = '-';
    } else if (value < 0) {
        uintToString(LargestUInt(-value), current);
        *--current = '-';
    } else {
        uintToString(LargestUInt(value), current);
    }
    assert(current >= buffer);
    return current;
}

String valueToString(LargestUInt value) {
    UIntToStringBuffer buffer;
    char* current = buffer + sizeof(buffer);
    uintToString(value, current);
    assert(current >= buffer);
    return current;
}

#if defined(JSON_HAS_INT64)

String valueToString(Int value) {
    return valueToString(LargestInt(value));
}

String valueToString(UInt value) {
    return valueToString(LargestUInt(value));
}

#endif  // # if defined(JSON_HAS_INT64)

namespace {
String valueToString(double value, bool useSpecialFloats, unsigned int precision, PrecisionType precisionType) {
    // Print into the buffer. We need not request the alternative representation
    // that always has a decimal point because JSON doesn't distinguish the
    // concepts of reals and integers.
    if (!isfinite(value)) {
        static const char* const reps[2][3] = {{"NaN", "-Infinity", "Infinity"}, {"null", "-1e+9999", "1e+9999"}};
        return reps[useSpecialFloats ? 0 : 1][isnan(value) ? 0 : (value < 0) ? 1 : 2];
    }

    String buffer(size_t(36), '\0');
    while (true) {
        int len = jsoncpp_snprintf(&*buffer.begin(), buffer.size(), (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f", precision,
                                   value);
        assert(len >= 0);
        auto wouldPrint = static_cast<size_t>(len);
        if (wouldPrint >= buffer.size()) {
            buffer.resize(wouldPrint + 1);
            continue;
        }
        buffer.resize(wouldPrint);
        break;
    }

    buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());

    // strip the zero padding from the right
    if (precisionType == PrecisionType::decimalPlaces) {
        buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end());
    }

    // try to ensure we preserve the fact that this was given to us as a double on
    // input
    if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {
        buffer += ".0";
    }
    return buffer;
}
}  // namespace

String valueToString(double value, unsigned int precision, PrecisionType precisionType) {
    return valueToString(value, false, precision, precisionType);
}

String valueToString(bool value) {
    return value ? "true" : "false";
}

static bool doesAnyCharRequireEscaping(char const* s, size_t n) {
    assert(s || !n);

    return std::any_of(s, s + n, [](unsigned char c) {
        return c == '\\' || c == '"' || c < 0x20 || c > 0x7F;
    });
}

static unsigned int utf8ToCodepoint(const char*& s, const char* e) {
    const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;

    unsigned int firstByte = static_cast<unsigned char>(*s);

    if (firstByte < 0x80)
        return firstByte;

    if (firstByte < 0xE0) {
        if (e - s < 2)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated = ((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);
        s += 1;
        // oversized encoded characters are invalid
        return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
    }

    if (firstByte < 0xF0) {
        if (e - s < 3)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated =
            ((firstByte & 0x0F) << 12) | ((static_cast<unsigned int>(s[1]) & 0x3F) << 6) | (static_cast<unsigned int>(s[2]) & 0x3F);
        s += 2;
        // surrogates aren't valid codepoints itself
        // shouldn't be UTF-8 encoded
        if (calculated >= 0xD800 && calculated <= 0xDFFF)
            return REPLACEMENT_CHARACTER;
        // oversized encoded characters are invalid
        return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
    }

    if (firstByte < 0xF8) {
        if (e - s < 4)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated = ((firstByte & 0x07) << 18) | ((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |
                                  ((static_cast<unsigned int>(s[2]) & 0x3F) << 6) | (static_cast<unsigned int>(s[3]) & 0x3F);
        s += 3;
        // oversized encoded characters are invalid
        return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
    }

    return REPLACEMENT_CHARACTER;
}

static const char hex2[] =
    "000102030405060708090a0b0c0d0e0f"
    "101112131415161718191a1b1c1d1e1f"
    "202122232425262728292a2b2c2d2e2f"
    "303132333435363738393a3b3c3d3e3f"
    "404142434445464748494a4b4c4d4e4f"
    "505152535455565758595a5b5c5d5e5f"
    "606162636465666768696a6b6c6d6e6f"
    "707172737475767778797a7b7c7d7e7f"
    "808182838485868788898a8b8c8d8e8f"
    "909192939495969798999a9b9c9d9e9f"
    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";

static String toHex16Bit(unsigned int x) {
    const unsigned int hi = (x >> 8) & 0xff;
    const unsigned int lo = x & 0xff;
    String result(4, ' ');
    result[0] = hex2[2 * hi];
    result[1] = hex2[2 * hi + 1];
    result[2] = hex2[2 * lo];
    result[3] = hex2[2 * lo + 1];
    return result;
}

static void appendRaw(String& result, unsigned ch) {
    result += static_cast<char>(ch);
}

static void appendHex(String& result, unsigned ch) {
    result.append("\\u").append(toHex16Bit(ch));
}

static String valueToQuotedStringN(const char* value, unsigned length, bool emitUTF8 = false) {
    if (value == nullptr)
        return "";

    if (!doesAnyCharRequireEscaping(value, length))
        return String("\"") + value + "\"";
    // We have to walk value and escape any special characters.
    // Appending to String is not efficient, but this should be rare.
    // (Note: forward slashes are *not* rare, but I am not escaping them.)
    String::size_type maxsize = length * 2 + 3;  // allescaped+quotes+NULL
    String result;
    result.reserve(maxsize);  // to avoid lots of mallocs
    result += "\"";
    char const* end = value + length;
    for (const char* c = value; c != end; ++c) {
        switch (*c) {
            case '\"':
                result += "\\\"";
                break;
            case '\\':
                result += "\\\\";
                break;
            case '\b':
                result += "\\b";
                break;
            case '\f':
                result += "\\f";
                break;
            case '\n':
                result += "\\n";
                break;
            case '\r':
                result += "\\r";
                break;
            case '\t':
                result += "\\t";
                break;
            // case '/':
            // Even though \/ is considered a legal escape in JSON, a bare
            // slash is also legal, so I see no reason to escape it.
            // (I hope I am not misunderstanding something.)
            // blep notes: actually escaping \/ may be useful in javascript to avoid </
            // sequence.
            // Should add a flag to allow this compatibility mode and prevent this
            // sequence from occurring.
            default: {
                if (emitUTF8) {
                    unsigned codepoint = static_cast<unsigned char>(*c);
                    if (codepoint < 0x20) {
                        appendHex(result, codepoint);
                    } else {
                        appendRaw(result, codepoint);
                    }
                } else {
                    unsigned codepoint = utf8ToCodepoint(c, end);  // modifies `c`
                    if (codepoint < 0x20) {
                        appendHex(result, codepoint);
                    } else if (codepoint < 0x80) {
                        appendRaw(result, codepoint);
                    } else if (codepoint < 0x10000) {
                        // Basic Multilingual Plane
                        appendHex(result, codepoint);
                    } else {
                        // Extended Unicode. Encode 20 bits as a surrogate pair.
                        codepoint -= 0x10000;
                        appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff));
                        appendHex(result, 0xdc00 + (codepoint & 0x3ff));
                    }
                }
            } break;
        }
    }
    result += "\"";
    return result;
}

String valueToQuotedString(const char* value) {
    return valueToQuotedStringN(value, static_cast<unsigned int>(strlen(value)));
}

// Class Writer
// //////////////////////////////////////////////////////////////////
Writer::~Writer() = default;

// Class FastWriter
// //////////////////////////////////////////////////////////////////

FastWriter::FastWriter()

    = default;

void FastWriter::enableYAMLCompatibility() {
    yamlCompatibilityEnabled_ = true;
}

void FastWriter::dropNullPlaceholders() {
    dropNullPlaceholders_ = true;
}

void FastWriter::omitEndingLineFeed() {
    omitEndingLineFeed_ = true;
}

String FastWriter::write(const Value& root) {
    document_.clear();
    writeValue(root);
    if (!omitEndingLineFeed_)
        document_ += '\n';
    return document_;
}

void FastWriter::writeValue(const Value& value) {
    switch (value.type()) {
        case nullValue:
            if (!dropNullPlaceholders_)
                document_ += "null";
            break;
        case intValue:
            document_ += valueToString(value.asLargestInt());
            break;
        case uintValue:
            document_ += valueToString(value.asLargestUInt());
            break;
        case realValue:
            document_ += valueToString(value.asDouble());
            break;
        case stringValue: {
            // Is NULL possible for value.string_? No.
            char const* str;
            char const* end;
            bool ok = value.getString(&str, &end);
            if (ok)
                document_ += valueToQuotedStringN(str, static_cast<unsigned>(end - str));
            break;
        }
        case booleanValue:
            document_ += valueToString(value.asBool());
            break;
        case arrayValue: {
            document_ += '[';
            ArrayIndex size = value.size();
            for (ArrayIndex index = 0; index < size; ++index) {
                if (index > 0)
                    document_ += ',';
                writeValue(value[index]);
            }
            document_ += ']';
        } break;
        case objectValue: {
            Value::Members members(value.getMemberNames());
            document_ += '{';
            for (auto it = members.begin(); it != members.end(); ++it) {
                const String& name = *it;
                if (it != members.begin())
                    document_ += ',';
                document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
                document_ += yamlCompatibilityEnabled_ ? ": " : ":";
                writeValue(value[name]);
            }
            document_ += '}';
        } break;
    }
}

// Class StyledWriter
// //////////////////////////////////////////////////////////////////

StyledWriter::StyledWriter() = default;

String StyledWriter::write(const Value& root) {
    document_.clear();
    addChildValues_ = false;
    indentString_.clear();
    writeCommentBeforeValue(root);
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    document_ += '\n';
    return document_;
}

void StyledWriter::writeValue(const Value& value) {
    switch (value.type()) {
        case nullValue:
            pushValue("null");
            break;
        case intValue:
            pushValue(valueToString(value.asLargestInt()));
            break;
        case uintValue:
            pushValue(valueToString(value.asLargestUInt()));
            break;
        case realValue:
            pushValue(valueToString(value.asDouble()));
            break;
        case stringValue: {
            // Is NULL possible for value.string_? No.
            char const* str;
            char const* end;
            bool ok = value.getString(&str, &end);
            if (ok)
                pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str)));
            else
                pushValue("");
            break;
        }
        case booleanValue:
            pushValue(valueToString(value.asBool()));
            break;
        case arrayValue:
            writeArrayValue(value);
            break;
        case objectValue: {
            Value::Members members(value.getMemberNames());
            if (members.empty())
                pushValue("{}");
            else {
                writeWithIndent("{");
                indent();
                auto it = members.begin();
                for (;;) {
                    const String& name = *it;
                    const Value& childValue = value[name];
                    writeCommentBeforeValue(childValue);
                    writeWithIndent(valueToQuotedString(name.c_str()));
                    document_ += " : ";
                    writeValue(childValue);
                    if (++it == members.end()) {
                        writeCommentAfterValueOnSameLine(childValue);
                        break;
                    }
                    document_ += ',';
                    writeCommentAfterValueOnSameLine(childValue);
                }
                unindent();
                writeWithIndent("}");
            }
        } break;
    }
}

void StyledWriter::writeArrayValue(const Value& value) {
    unsigned size = value.size();
    if (size == 0)
        pushValue("[]");
    else {
        bool isArrayMultiLine = isMultilineArray(value);
        if (isArrayMultiLine) {
            writeWithIndent("[");
            indent();
            bool hasChildValue = !childValues_.empty();
            unsigned index = 0;
            for (;;) {
                const Value& childValue = value[index];
                writeCommentBeforeValue(childValue);
                if (hasChildValue)
                    writeWithIndent(childValues_[index]);
                else {
                    writeIndent();
                    writeValue(childValue);
                }
                if (++index == size) {
                    writeCommentAfterValueOnSameLine(childValue);
                    break;
                }
                document_ += ',';
                writeCommentAfterValueOnSameLine(childValue);
            }
            unindent();
            writeWithIndent("]");
        } else  // output on a single line
        {
            assert(childValues_.size() == size);
            document_ += "[ ";
            for (unsigned index = 0; index < size; ++index) {
                if (index > 0)
                    document_ += ", ";
                document_ += childValues_[index];
            }
            document_ += " ]";
        }
    }
}

bool StyledWriter::isMultilineArray(const Value& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
        const Value& childValue = value[index];
        isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty());
    }
    if (!isMultiLine)  // check if line length > max line length
    {
        childValues_.reserve(size);
        addChildValues_ = true;
        ArrayIndex lineLength = 4 + (size - 1) * 2;  // '[ ' + ', '*n + ' ]'
        for (ArrayIndex index = 0; index < size; ++index) {
            if (hasCommentForValue(value[index])) {
                isMultiLine = true;
            }
            writeValue(value[index]);
            lineLength += static_cast<ArrayIndex>(childValues_[index].length());
        }
        addChildValues_ = false;
        isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
}

void StyledWriter::pushValue(const String& value) {
    if (addChildValues_)
        childValues_.push_back(value);
    else
        document_ += value;
}

void StyledWriter::writeIndent() {
    if (!document_.empty()) {
        char last = document_[document_.length() - 1];
        if (last == ' ')  // already indented
            return;
        if (last != '\n')  // Comments may add new-line
            document_ += '\n';
    }
    document_ += indentString_;
}

void StyledWriter::writeWithIndent(const String& value) {
    writeIndent();
    document_ += value;
}

void StyledWriter::indent() {
    indentString_ += String(indentSize_, ' ');
}

void StyledWriter::unindent() {
    assert(indentString_.size() >= indentSize_);
    indentString_.resize(indentString_.size() - indentSize_);
}

void StyledWriter::writeCommentBeforeValue(const Value& root) {
    if (!root.hasComment(commentBefore))
        return;

    document_ += '\n';
    writeIndent();
    const String& comment = root.getComment(commentBefore);
    String::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
        document_ += *iter;
        if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
            writeIndent();
        ++iter;
    }

    // Comments are stripped of trailing newlines, so add one here
    document_ += '\n';
}

void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
    if (root.hasComment(commentAfterOnSameLine))
        document_ += " " + root.getComment(commentAfterOnSameLine);

    if (root.hasComment(commentAfter)) {
        document_ += '\n';
        document_ += root.getComment(commentAfter);
        document_ += '\n';
    }
}

bool StyledWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || value.hasComment(commentAfter);
}

// Class StyledStreamWriter
// //////////////////////////////////////////////////////////////////

StyledStreamWriter::StyledStreamWriter(String indentation)
    : document_(nullptr)
    , indentation_(std::move(indentation))
    , addChildValues_()
    , indented_(false) {}

void StyledStreamWriter::write(OStream& out, const Value& root) {
    document_ = &out;
    addChildValues_ = false;
    indentString_.clear();
    indented_ = true;
    writeCommentBeforeValue(root);
    if (!indented_)
        writeIndent();
    indented_ = true;
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    *document_ << "\n";
    document_ = nullptr;  // Forget the stream, for safety.
}

void StyledStreamWriter::writeValue(const Value& value) {
    switch (value.type()) {
        case nullValue:
            pushValue("null");
            break;
        case intValue:
            pushValue(valueToString(value.asLargestInt()));
            break;
        case uintValue:
            pushValue(valueToString(value.asLargestUInt()));
            break;
        case realValue:
            pushValue(valueToString(value.asDouble()));
            break;
        case stringValue: {
            // Is NULL possible for value.string_? No.
            char const* str;
            char const* end;
            bool ok = value.getString(&str, &end);
            if (ok)
                pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str)));
            else
                pushValue("");
            break;
        }
        case booleanValue:
            pushValue(valueToString(value.asBool()));
            break;
        case arrayValue:
            writeArrayValue(value);
            break;
        case objectValue: {
            Value::Members members(value.getMemberNames());
            if (members.empty())
                pushValue("{}");
            else {
                writeWithIndent("{");
                indent();
                auto it = members.begin();
                for (;;) {
                    const String& name = *it;
                    const Value& childValue = value[name];
                    writeCommentBeforeValue(childValue);
                    writeWithIndent(valueToQuotedString(name.c_str()));
                    *document_ << " : ";
                    writeValue(childValue);
                    if (++it == members.end()) {
                        writeCommentAfterValueOnSameLine(childValue);
                        break;
                    }
                    *document_ << ",";
                    writeCommentAfterValueOnSameLine(childValue);
                }
                unindent();
                writeWithIndent("}");
            }
        } break;
    }
}

void StyledStreamWriter::writeArrayValue(const Value& value) {
    unsigned size = value.size();
    if (size == 0)
        pushValue("[]");
    else {
        bool isArrayMultiLine = isMultilineArray(value);
        if (isArrayMultiLine) {
            writeWithIndent("[");
            indent();
            bool hasChildValue = !childValues_.empty();
            unsigned index = 0;
            for (;;) {
                const Value& childValue = value[index];
                writeCommentBeforeValue(childValue);
                if (hasChildValue)
                    writeWithIndent(childValues_[index]);
                else {
                    if (!indented_)
                        writeIndent();
                    indented_ = true;
                    writeValue(childValue);
                    indented_ = false;
                }
                if (++index == size) {
                    writeCommentAfterValueOnSameLine(childValue);
                    break;
                }
                *document_ << ",";
                writeCommentAfterValueOnSameLine(childValue);
            }
            unindent();
            writeWithIndent("]");
        } else  // output on a single line
        {
            assert(childValues_.size() == size);
            *document_ << "[ ";
            for (unsigned index = 0; index < size; ++index) {
                if (index > 0)
                    *document_ << ", ";
                *document_ << childValues_[index];
            }
            *document_ << " ]";
        }
    }
}

bool StyledStreamWriter::isMultilineArray(const Value& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
        const Value& childValue = value[index];
        isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty());
    }
    if (!isMultiLine)  // check if line length > max line length
    {
        childValues_.reserve(size);
        addChildValues_ = true;
        ArrayIndex lineLength = 4 + (size - 1) * 2;  // '[ ' + ', '*n + ' ]'
        for (ArrayIndex index = 0; index < size; ++index) {
            if (hasCommentForValue(value[index])) {
                isMultiLine = true;
            }
            writeValue(value[index]);
            lineLength += static_cast<ArrayIndex>(childValues_[index].length());
        }
        addChildValues_ = false;
        isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
}

void StyledStreamWriter::pushValue(const String& value) {
    if (addChildValues_)
        childValues_.push_back(value);
    else
        *document_ << value;
}

void StyledStreamWriter::writeIndent() {
    // blep intended this to look at the so-far-written string
    // to determine whether we are already indented, but
    // with a stream we cannot do that. So we rely on some saved state.
    // The caller checks indented_.
    *document_ << '\n' << indentString_;
}

void StyledStreamWriter::writeWithIndent(const String& value) {
    if (!indented_)
        writeIndent();
    *document_ << value;
    indented_ = false;
}

void StyledStreamWriter::indent() {
    indentString_ += indentation_;
}

void StyledStreamWriter::unindent() {
    assert(indentString_.size() >= indentation_.size());
    indentString_.resize(indentString_.size() - indentation_.size());
}

void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
    if (!root.hasComment(commentBefore))
        return;

    if (!indented_)
        writeIndent();
    const String& comment = root.getComment(commentBefore);
    String::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
        *document_ << *iter;
        if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
            // writeIndent();  // would include newline
            *document_ << indentString_;
        ++iter;
    }
    indented_ = false;
}

void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
    if (root.hasComment(commentAfterOnSameLine))
        *document_ << ' ' << root.getComment(commentAfterOnSameLine);

    if (root.hasComment(commentAfter)) {
        writeIndent();
        *document_ << root.getComment(commentAfter);
    }
    indented_ = false;
}

bool StyledStreamWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || value.hasComment(commentAfter);
}

//////////////////////////
// BuiltStyledStreamWriter

/// Scoped enums are not available until C++11.
struct CommentStyle {
    /// Decide whether to write comments.
    enum Enum {
        None,  ///< Drop all comments.
        Most,  ///< Recover odd behavior of previous versions (not implemented yet).
        All    ///< Keep all comments.
    };
};

struct BuiltStyledStreamWriter : public StreamWriter {
    BuiltStyledStreamWriter(String indentation,
                            CommentStyle::Enum cs,
                            String colonSymbol,
                            String nullSymbol,
                            String endingLineFeedSymbol,
                            bool useSpecialFloats,
                            bool emitUTF8,
                            unsigned int precision,
                            PrecisionType precisionType);
    int write(Value const& root, OStream* sout) override;

private:
    void writeValue(Value const& value);
    void writeArrayValue(Value const& value);
    bool isMultilineArray(Value const& value);
    void pushValue(String const& value);
    void writeIndent();
    void writeWithIndent(String const& value);
    void indent();
    void unindent();
    void writeCommentBeforeValue(Value const& root);
    void writeCommentAfterValueOnSameLine(Value const& root);
    static bool hasCommentForValue(const Value& value);

    using ChildValues = std::vector<String>;

    ChildValues childValues_;
    String indentString_;
    unsigned int rightMargin_;
    String indentation_;
    CommentStyle::Enum cs_;
    String colonSymbol_;
    String nullSymbol_;
    String endingLineFeedSymbol_;
    bool addChildValues_ : 1;
    bool indented_ : 1;
    bool useSpecialFloats_ : 1;
    bool emitUTF8_ : 1;
    unsigned int precision_;
    PrecisionType precisionType_;
};
BuiltStyledStreamWriter::BuiltStyledStreamWriter(String indentation,
                                                 CommentStyle::Enum cs,
                                                 String colonSymbol,
                                                 String nullSymbol,
                                                 String endingLineFeedSymbol,
                                                 bool useSpecialFloats,
                                                 bool emitUTF8,
                                                 unsigned int precision,
                                                 PrecisionType precisionType)
    : rightMargin_(74)
    , indentation_(std::move(indentation))
    , cs_(cs)
    , colonSymbol_(std::move(colonSymbol))
    , nullSymbol_(std::move(nullSymbol))
    , endingLineFeedSymbol_(std::move(endingLineFeedSymbol))
    , addChildValues_(false)
    , indented_(false)
    , useSpecialFloats_(useSpecialFloats)
    , emitUTF8_(emitUTF8)
    , precision_(precision)
    , precisionType_(precisionType) {}
int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) {
    sout_ = sout;
    addChildValues_ = false;
    indented_ = true;
    indentString_.clear();
    writeCommentBeforeValue(root);
    if (!indented_)
        writeIndent();
    indented_ = true;
    writeValue(root);
    writeCommentAfterValueOnSameLine(root);
    *sout_ << endingLineFeedSymbol_;
    sout_ = nullptr;
    return 0;
}
void BuiltStyledStreamWriter::writeValue(Value const& value) {
    switch (value.type()) {
        case nullValue:
            pushValue(nullSymbol_);
            break;
        case intValue:
            pushValue(valueToString(value.asLargestInt()));
            break;
        case uintValue:
            pushValue(valueToString(value.asLargestUInt()));
            break;
        case realValue:
            pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, precisionType_));
            break;
        case stringValue: {
            // Is NULL is possible for value.string_? No.
            char const* str;
            char const* end;
            bool ok = value.getString(&str, &end);
            if (ok)
                pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str), emitUTF8_));
            else
                pushValue("");
            break;
        }
        case booleanValue:
            pushValue(valueToString(value.asBool()));
            break;
        case arrayValue:
            writeArrayValue(value);
            break;
        case objectValue: {
            Value::Members members(value.getMemberNames());
            if (members.empty())
                pushValue("{}");
            else {
                writeWithIndent("{");
                indent();
                auto it = members.begin();
                for (;;) {
                    String const& name = *it;
                    Value const& childValue = value[name];
                    writeCommentBeforeValue(childValue);
                    writeWithIndent(valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()), emitUTF8_));
                    *sout_ << colonSymbol_;
                    writeValue(childValue);
                    if (++it == members.end()) {
                        writeCommentAfterValueOnSameLine(childValue);
                        break;
                    }
                    *sout_ << ",";
                    writeCommentAfterValueOnSameLine(childValue);
                }
                unindent();
                writeWithIndent("}");
            }
        } break;
    }
}

void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
    unsigned size = value.size();
    if (size == 0)
        pushValue("[]");
    else {
        bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);
        if (isMultiLine) {
            writeWithIndent("[");
            indent();
            bool hasChildValue = !childValues_.empty();
            unsigned index = 0;
            for (;;) {
                Value const& childValue = value[index];
                writeCommentBeforeValue(childValue);
                if (hasChildValue)
                    writeWithIndent(childValues_[index]);
                else {
                    if (!indented_)
                        writeIndent();
                    indented_ = true;
                    writeValue(childValue);
                    indented_ = false;
                }
                if (++index == size) {
                    writeCommentAfterValueOnSameLine(childValue);
                    break;
                }
                *sout_ << ",";
                writeCommentAfterValueOnSameLine(childValue);
            }
            unindent();
            writeWithIndent("]");
        } else  // output on a single line
        {
            assert(childValues_.size() == size);
            *sout_ << "[";
            if (!indentation_.empty())
                *sout_ << " ";
            for (unsigned index = 0; index < size; ++index) {
                if (index > 0)
                    *sout_ << ((!indentation_.empty()) ? ", " : ",");
                *sout_ << childValues_[index];
            }
            if (!indentation_.empty())
                *sout_ << " ";
            *sout_ << "]";
        }
    }
}

bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {
    ArrayIndex const size = value.size();
    bool isMultiLine = size * 3 >= rightMargin_;
    childValues_.clear();
    for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
        Value const& childValue = value[index];
        isMultiLine = ((childValue.isArray() || childValue.isObject()) && !childValue.empty());
    }
    if (!isMultiLine)  // check if line length > max line length
    {
        childValues_.reserve(size);
        addChildValues_ = true;
        ArrayIndex lineLength = 4 + (size - 1) * 2;  // '[ ' + ', '*n + ' ]'
        for (ArrayIndex index = 0; index < size; ++index) {
            if (hasCommentForValue(value[index])) {
                isMultiLine = true;
            }
            writeValue(value[index]);
            lineLength += static_cast<ArrayIndex>(childValues_[index].length());
        }
        addChildValues_ = false;
        isMultiLine = isMultiLine || lineLength >= rightMargin_;
    }
    return isMultiLine;
}

void BuiltStyledStreamWriter::pushValue(String const& value) {
    if (addChildValues_)
        childValues_.push_back(value);
    else
        *sout_ << value;
}

void BuiltStyledStreamWriter::writeIndent() {
    // blep intended this to look at the so-far-written string
    // to determine whether we are already indented, but
    // with a stream we cannot do that. So we rely on some saved state.
    // The caller checks indented_.

    if (!indentation_.empty()) {
        // In this case, drop newlines too.
        *sout_ << '\n' << indentString_;
    }
}

void BuiltStyledStreamWriter::writeWithIndent(String const& value) {
    if (!indented_)
        writeIndent();
    *sout_ << value;
    indented_ = false;
}

void BuiltStyledStreamWriter::indent() {
    indentString_ += indentation_;
}

void BuiltStyledStreamWriter::unindent() {
    assert(indentString_.size() >= indentation_.size());
    indentString_.resize(indentString_.size() - indentation_.size());
}

void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
    if (cs_ == CommentStyle::None)
        return;
    if (!root.hasComment(commentBefore))
        return;

    if (!indented_)
        writeIndent();
    const String& comment = root.getComment(commentBefore);
    String::const_iterator iter = comment.begin();
    while (iter != comment.end()) {
        *sout_ << *iter;
        if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
            // writeIndent();  // would write extra newline
            *sout_ << indentString_;
        ++iter;
    }
    indented_ = false;
}

void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
    if (cs_ == CommentStyle::None)
        return;
    if (root.hasComment(commentAfterOnSameLine))
        *sout_ << " " + root.getComment(commentAfterOnSameLine);

    if (root.hasComment(commentAfter)) {
        writeIndent();
        *sout_ << root.getComment(commentAfter);
    }
}

// static
bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
    return value.hasComment(commentBefore) || value.hasComment(commentAfterOnSameLine) || value.hasComment(commentAfter);
}

///////////////
// StreamWriter

StreamWriter::StreamWriter()
    : sout_(nullptr) {}
StreamWriter::~StreamWriter() = default;
StreamWriter::Factory::~Factory() = default;
StreamWriterBuilder::StreamWriterBuilder() {
    setDefaults(&settings_);
}
StreamWriterBuilder::~StreamWriterBuilder() = default;
StreamWriter* StreamWriterBuilder::newStreamWriter() const {
    const String indentation = settings_["indentation"].asString();
    const String cs_str = settings_["commentStyle"].asString();
    const String pt_str = settings_["precisionType"].asString();
    const bool eyc = settings_["enableYAMLCompatibility"].asBool();
    const bool dnp = settings_["dropNullPlaceholders"].asBool();
    const bool usf = settings_["useSpecialFloats"].asBool();
    const bool emitUTF8 = settings_["emitUTF8"].asBool();
    unsigned int pre = settings_["precision"].asUInt();
    CommentStyle::Enum cs = CommentStyle::All;
    if (cs_str == "All") {
        cs = CommentStyle::All;
    } else if (cs_str == "None") {
        cs = CommentStyle::None;
    } else {
        throwRuntimeError("commentStyle must be 'All' or 'None'");
    }
    PrecisionType precisionType(significantDigits);
    if (pt_str == "significant") {
        precisionType = PrecisionType::significantDigits;
    } else if (pt_str == "decimal") {
        precisionType = PrecisionType::decimalPlaces;
    } else {
        throwRuntimeError("precisionType must be 'significant' or 'decimal'");
    }
    String colonSymbol = " : ";
    if (eyc) {
        colonSymbol = ": ";
    } else if (indentation.empty()) {
        colonSymbol = ":";
    }
    String nullSymbol = "null";
    if (dnp) {
        nullSymbol.clear();
    }
    if (pre > 17)
        pre = 17;
    String endingLineFeedSymbol;
    return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol, endingLineFeedSymbol, usf, emitUTF8, pre, precisionType);
}

bool StreamWriterBuilder::validate(nim_cpp_wrapper_util::Json::Value* invalid) const {
    static const auto& valid_keys = *new std::set<String>{
        "indentation", "commentStyle", "enableYAMLCompatibility", "dropNullPlaceholders", "useSpecialFloats",
        "emitUTF8",    "precision",    "precisionType",
    };
    for (auto si = settings_.begin(); si != settings_.end(); ++si) {
        auto key = si.name();
        if (valid_keys.count(key))
            continue;
        if (invalid)
            (*invalid)[std::move(key)] = *si;
        else
            return false;
    }
    return invalid ? invalid->empty() : true;
}

Value& StreamWriterBuilder::operator[](const String& key) {
    return settings_[key];
}
// static
void StreamWriterBuilder::setDefaults(nim_cpp_wrapper_util::Json::Value* settings) {
    //! [StreamWriterBuilderDefaults]
    (*settings)["commentStyle"] = "All";
    (*settings)["indentation"] = "\t";
    (*settings)["enableYAMLCompatibility"] = false;
    (*settings)["dropNullPlaceholders"] = false;
    (*settings)["useSpecialFloats"] = false;
    (*settings)["emitUTF8"] = false;
    (*settings)["precision"] = 17;
    (*settings)["precisionType"] = "significant";
    //! [StreamWriterBuilderDefaults]
}

String writeString(StreamWriter::Factory const& factory, Value const& root) {
    OStringStream sout;
    StreamWriterPtr const writer(factory.newStreamWriter());
    writer->write(root, &sout);
    return sout.str();
}

OStream& operator<<(OStream& sout, Value const& root) {
    StreamWriterBuilder builder;
    StreamWriterPtr const writer(builder.newStreamWriter());
    writer->write(root, &sout);
    return sout;
}

}  // namespace Json
}  // namespace nim_cpp_wrapper_util
